#include <unistd.h>
#include <stdio.h>
#include <dirent.h>
#include <types.h>
#include <stddef.h>
#include <math.h>
#include <sys/types.h>
#include <sys/syscall.h>

#define _MAX_DIRDES_NR 32

DIR __dirdes_table[_MAX_DIRDES_NR] = {
    {0, -1},
};

static dirent_t __dirent_buff;

// parse assign path
// see follow:
// path: "//abc/bc"    result: "a/b"
char *parse_path_after(char *path, char *out)
{
    // if present one or more char "/",we need jump to next char
    while (*path == '/')
        path++;

    while ((*path != '\0') && (*path != '/'))
    {
        *out++ = *path++;
    }

    if (path[0] == '\0')
        return NULL;
    return path;
}

void wash_path(char *old, char *new)
{
    if (!old || !new)
        return;

    char *sub_path = old;
    char name[MAX_PATH_LEN+1];
    char *slash_ptr;

    memset(name, 0, MAX_PATH_LEN);

    sub_path = parse_path_after(sub_path, name);
    if (!name[0])
    {
        // return root
        new[0] = '/';
        new[1] = '\0';
        return;
    }
    new[0] = '\0';
    strcat(new, "/");
    while (name[0])
    {
        if (!strcmp("..", name))
        {
            slash_ptr = (char *)strrchr(new, '/'); // get down level path
            if (slash_ptr != new)
                *slash_ptr = 0;
            else // up level is '/'
                *(slash_ptr + 1) = 0;
        }
        else
        {
            if (strcmp(".", name))
            {
                if (strcmp(new, "/"))
                    strcat(new, "/");
                strcat(new, name);
            }
        }
        memset(name, 0, MAX_PATH_LEN);
        // get down lovel path
        if (sub_path)
        {
            sub_path = parse_path_after(sub_path, name);
        }
    }

    int len;
    while ((len = strlen(new)) > 0)
    {
        if (new[len - 1] == '\n' || new[len - 1] == '\r')
        {
            new[len - 1] = '\0';
        }
        else
        {
            break;
        }
    }
}


void buildpath(char *path, char *out)
{
    char abspath[MAX_PATH_LEN+1];
    char *p = NULL;

    // only root direct exit
    if (*path == '/' && *(path + 1) == '\0')
    {
        out[0]='/';
        out[1]='\0';
        return;
    }

    memset(abspath, 0, MAX_PATH_LEN);

    make_abs_path(path, abspath);
    p = strchr(abspath, '/');
    wash_path(p, out);
}

void make_abs_path(char *path, char *abs)
{
    char *p;

    // if is abs path
    if (*path != '/')
    {
        // get current work dir
        if (!getcwd(abs, MAX_PATH_LEN))
        {
            p = strchr(abs, '/');
            if (p != NULL)
            {
                if ((p[0] == '/' && p[1] != '\0'))
                    strcat(abs, "/");
            }
        }
    }

    // into root dir
    if (path[0] == '/' && path[1] == '\0')
    {
        abs[0] = '/';
        abs[1] = '\0';
    }
    else
    {
        // if no abspath,then add to current work path
        // or is abspath
        strcat(abs,"/");
        strcat(abs, path);
    }
}

int chdir(const char *path)
{
    const char *p;
    char full[MAX_PATH_LEN+1];

    if (!(*path == '/' && *(path + 1) == '\0'))
    {
        buildpath((char *)path, full);
        p = full;
    }
    else
    {
        p = path;
    }
    return syscall1(int, SYS_CHDIR, (uintptr_t)p);
}

char *getcwd(char *buff, int size)
{
    char *_buff = buff;
    if (!buff)
    {
        _buff = malloc(MAX_PATH_LEN);
        if (!_buff)
            return NULL;
        memset(_buff, 0, MAX_PATH_LEN);
    }
    if (syscall2(int, SYS_GETCWD, (uintptr_t)_buff, size) < 0)
    {
        if (!buff && _buff)
        {
            free(_buff);
            _buff = NULL;
        }
    }
    return _buff;
}

static DIR *__alloc_dirdes()
{
    int i;
    for (i = 0; i < _MAX_DIRDES_NR; i++)
    {
        if (!__dirdes_table[i].flags) // free
        {
            __dirdes_table[i].flags = 1; // alloc
            return &__dirdes_table[i];
        }
    }
}

static void __free_dirdes(DIR *_dir)
{
    _dir->flags = 0;
    _dir->diridx = -1;
}

DIR *opendir(const char *path)
{
    char abs_path[MAX_PATH_LEN+1];
    char *p;
    DIR *dir;
    dir_t diridx;

    if (!path)
        return NULL;

    memset(abs_path, 0, MAX_PATH_LEN);

    buildpath((char *)path, abs_path);
    p = abs_path;

    // open dir
    diridx = syscall1(int, SYS_OPENDIR, (uintptr_t)p);
    if (diridx < 0)
    {
        return NULL;
    }

    // alloc DIR
    dir = __alloc_dirdes();
    if (!dir)
    {
        printf("alloc dirdes failed!\n");
        return NULL;
    }
    dir->diridx = diridx;
    return dir;
}

int closedir(DIR *dir)
{
    if (!DIR_IN_RANAGE(dir))
        return -1;

    if (!dir->flags)
        return -1;

    if (syscall1(int, SYS_CLOSEDIR, dir->diridx) < 0)
        return -1;

    __free_dirdes(dir);
    return 0;
}

dirent_t *readdir(DIR *dir)
{
    if (!DIR_IN_RANAGE(dir))
        return NULL;

    if (!dir->flags)
        return NULL;

    memset(&__dirent_buff, 0, sizeof(dirent_t));
    if (syscall2(int, SYS_READDIR, dir->diridx, (uintptr_t)&__dirent_buff) < 0)
        return NULL;
    return &__dirent_buff;
}

int rewinddir(DIR *dir)
{
    if (!DIR_IN_RANAGE(dir))
        return -1;
    if (!dir->flags)
        return -1;

    return syscall1(int, SYS_REWINDDIR, dir->diridx);
}

int mkdir(const char *path, mode_t mode)
{
    char abs_path[MAX_PATH_LEN+1];
    char *p;

    if (!path)
        return -1;
    buildpath((char *)path, abs_path);

    p = abs_path;
    return syscall2(int, SYS_MKDIR, (uintptr_t)p, mode);
}

int rmdir(const char *path)
{
    char abs_path[MAX_PATH_LEN+1];
    char *p;

    if (!path)
        return -1;
    buildpath((char *)path, abs_path);
    p = abs_path;
    return syscall1(int, SYS_RMDIR, (uintptr_t)p);
}

int _rename(const char *source, const char *targe)
{
    char abs_path0[MAX_PATH_LEN+1];
    char abs_path1[MAX_PATH_LEN+1];
    char *src, *dst;

    if (!source || !targe)
        return -1;
    buildpath((char *)source, abs_path0);
    buildpath((char *)targe, abs_path1);

    src = (char *)source;
    dst = (char *)targe;

    return syscall2(int, SYS_RENAME, (uintptr_t)src, (uintptr_t)dst);
}